//
//  ImageCache.m
//  RBIOSBase
//
//  Created by ren on 20/11/2017.
//  Copyright © 2017 bryan. All rights reserved.
//

#import "ImageCache.h"
#import "CommonDefine.h"
#import <CommonCrypto/CommonDigest.h>
#import <UIKit/UIKit.h>
#import "ImageCacheConfig.h"
#import "UIImage+MultiFormat123.h"

@interface AutoPurgeCache12 : NSCache

@end

@implementation AutoPurgeCache12

@end

FOUNDATION_STATIC_INLINE NSUInteger CacheCostForImage(UIImage *image) {
	return image.size.height * image.size.width * image.scale * image.scale;
}

@interface ImageCache ()

@property (nonatomic, strong) NSCache *memCache;
@property (nonatomic, strong) NSString *diskCachePath;
@property (nonatomic, strong) NSMutableArray<NSString *> *customPaths;

@property (nonatomic, SDDispatchQueueSetterSementics) dispatch_queue_t ioQueue;

@end

@implementation ImageCache {
	NSFileManager *_fileManager;
}

+ (instancetype)shareCache
{
	dispatch_once_t onceToken;
	static ImageCache *instance = nil;
	dispatch_once(&onceToken, ^{
		instance = [self init];
	});
	return instance;
}

- (instancetype)init
{
	return [self initWithNameSpace:@"default"];
}

- (instancetype)initWithNameSpace:(NSString *)nameSpace
{
	NSString *path = [self makeDiskCachePath:nameSpace];
	return [self initWithNameSpace:nameSpace diskCacheDirectory:path];
}

- (instancetype)initWithNameSpace:(NSString *)nameSpace diskCacheDirectory:(NSString *)directory
{
	if (self = [super init]) {
		NSString *fullNameSpace = [@"com.bryan.ImageCache." stringByAppendingString:nameSpace];
		_ioQueue = dispatch_queue_create("com.bryan.ImageCache", DISPATCH_QUEUE_SERIAL);
		
		_config = [ImageCacheConfig new];
		
		_memCache = [[AutoPurgeCache12 alloc] init];
		_memCache.name = fullNameSpace;
		
		if (directory != nil) {
			_diskCachePath = [directory stringByAppendingPathComponent:fullNameSpace];
		}
		else {
			NSString *path = [self makeDiskCachePath:nameSpace];
			_diskCachePath = path;
		}
		
		dispatch_sync(_ioQueue, ^{
			_fileManager = [NSFileManager new];
		});

	}
	return self;
}

- (void)checkIfQueueIsIOQueue {
	const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);//当前队列
	const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);//自定义io队列
	if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
		NSLog(@"This method should be called from the ioQueue");
	}
}

#pragma mark - Cache paths

- (void)addReadOnlyCachePath:(NSString *)path
{
	if (!self.customPaths) {
		self.customPaths = [NSMutableArray new];
	}
	
	if (![self.customPaths containsObject:path]) {
		[self.customPaths addObject:path];
	}
}

- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path
{
	NSString *fileName = [self cachedFileNameForKey:key];
	return [path stringByAppendingPathComponent:fileName];
}

- (NSString *)defaultCachePathForKey:(NSString *)key
{
	return [self cachePathForKey:key inPath:self.diskCachePath];
}

//key的MD5，对URL进行MD5处理
- (NSString *)cachedFileNameForKey:(NSString *)key
{
	const char *str = key.UTF8String;//如果在block中使用C语言的char[],需要用char *替代
	if (str == NULL) {
		str = "";
	}
	unsigned char r[CC_MD5_DIGEST_LENGTH];
	CC_MD5(str, (CC_LONG)strlen(str), r);
	NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@", r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
	return filename;
}

- (NSString *)makeDiskCachePath:(NSString *)fullNamespace
{
	NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);//Library/Caches 目录下，iTunes不会备份这个目录。sandbox（沙盒）包含 App内容块，Documents，Library，tmp，文件夹
	return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

#pragma mark - Store Ops

- (void)storeImage:(UIImage *)image forKey:(NSString *)key completion:(WebImageNoParamsBlock)completionBlock
{
	[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}

- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk completion:(WebImageNoParamsBlock)completionBlock
{
	[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}

- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk completion:(WebImageNoParamsBlock)completionBlock
{
	if (!image || !key) {
		if (completionBlock) {//先判断是否为nil，再调用，否则会crash
			completionBlock();
		}
		return;
	}
	if (self.config.shouldCacheImagesInMemory) {
		NSUInteger cost = CacheCostForImage(image);
		[self.memCache setObject:image forKey:key cost:cost];
	}
	
	if (toDisk) {
		dispatch_async(self.ioQueue, ^{
			NSData *data = imageData;
			
			if (!data && image) {
				
			}
			[self storeImageDataToDisk:data forKey:key];
			if (completionBlock) {
				dispatch_async(dispatch_get_main_queue(), ^{
					completionBlock();
				});
			}
		});
	}
	else {
		if (completionBlock) {
			completionBlock();
		}
	}
}

- (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key {
	if (!imageData || !key) {
		return;
	}
	
	[self checkIfQueueIsIOQueue];
	
	if (![_fileManager fileExistsAtPath:_diskCachePath]) {
		[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
	}
	//
	NSString *cachePathForKey = [self defaultCachePathForKey:key];
	
	NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
	
	[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
	
	if (self.config.shouldDisableiCloud) {
		[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
	}
}

#pragma mark - Query and Retrive Ops

- (void)diskImageExitsWithKey:(NSString *)key completion:(WebImageCheckCacheCompletionBlock)completionBlock
{
	dispatch_async(self.ioQueue, ^{
		BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
		
		if (!exists) {
			exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
		}
		
		if (completionBlock) {
			dispatch_async(dispatch_get_main_queue(), ^{
				completionBlock(exists);
			});
		}
	});
}

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key
{
	UIImage *image = [self imageFromMemoryCacheForKey:key];
	if (image) {
		return image;
	}
	
	image = [self imageFromDiskCacheForKey:key];
	return image;
}

- (UIImage *)imageFromDiskCacheForKey:(NSString *)key
{
	UIImage *diskImage = [self diskImageForKey:key];//从磁盘取出
	if (diskImage && self.config.shouldCacheImagesInMemory) {//如果存在，配置可以缓存到内存
		NSUInteger cost = CacheCostForImage(diskImage);
		[self.memCache setObject:diskImage forKey:key cost:cost];//放入缓存
	}
	return diskImage;
}

- (UIImage *)diskImageForKey:(NSString *)key
{
	NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
	if (data) {
		UIImage *image = [UIImage br_imageWithData:data];
		return image;
	}
	else
		return nil;
}

- (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key
{
	NSString *defaultPath = [self defaultCachePathForKey:key];
	NSData *data = [NSData dataWithContentsOfFile:defaultPath];
	if (data) {
		return data;
	}
	data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension];
	if (data) {
		return data;
	}
	
	NSArray<NSString *> *customPaths = [self.customPaths copy];
	for (NSString *path in customPaths) {
		NSString *filePath = [self cachePathForKey:key inPath:path];
		NSData *imageData = [NSData dataWithContentsOfFile:filePath];
		if (imageData) {
			return imageData;
		}
		
		imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension];
		if (imageData) {
			return imageData;
		}
	}
	return nil;
}

@end
